class RackProctitle
  def initialize(app, options = nil)
    @app = app
    
    @title = "#{(options && options.delete(:prefix)) || 'rack'}"
    @title << "/#{APPLICATION_VERSION}" if defined?(APPLICATION_VERSION)
    @title << " [%d/%d]: %s"
    
    @mutex = Mutex.new
    @request_threads = []
    @total_request_count = 0
    
    start_updater_thread
  end

  def call(env)
    ensure_updater_thread_running
    Thread.current[:request_str] = ((env["REQUEST_URI"].nil? || env["REQUEST_URI"].empty?) ? "/" : env["REQUEST_URI"]).split("?", 2)[0]
    Thread.current[:arrived_at] = Time.now.to_f
    @mutex.synchronize do
      @request_threads.push(Thread.current)
      set_request_list_title
    end
    
    begin
      @app.call(env)
    ensure
      @mutex.synchronize do
        @total_request_count += 1

        @last_time = Time.now.to_f - Thread.current[:arrived_at].to_f
        @last_request_str = Thread.current[:request_str].to_s

        @request_threads.delete(Thread.current)
        set_request_list_title
      end
    end
  end

  protected
  # This exists for cases like unicorn where the instance initialized and then the process is forked.
  # Threads don't live through forks, so we have to start a new one inside each new process.
  def ensure_updater_thread_running
    if @pid != ::Process.pid
      start_updater_thread
    end
  end
  
  def start_updater_thread
    @updater_thread.kill if @updater_thread
    @pid = ::Process.pid
    @updater_thread = Thread.new do
      while true
        @mutex.synchronize do
          set_request_list_title
        end
        sleep 0.5
      end
    end
  end
  
  def set_request_list_title
    current_status = if @request_threads.empty?
      idle_message
    else
      now = Time.now.to_f
      list = @request_threads.inject([]) do |str, thread|
        str << "#{time_delta_abbriv(now - thread[:arrived_at])} #{thread[:request_str]}"
      end.join(" | ")
      "handling #{list}"
    end
    
    $0 = @title % [@request_threads.size, @total_request_count, current_status]
  end

  def idle_message
    str = "idle#{' '*20}" # the spacing is for visual distinction in a list of processes.
    str << "[last #{time_delta_abbriv(@last_time)} #{@last_request_str}]" if @last_time && @last_request_str
    str
  end
  
  TIME_DELTA_STRS = %w{%.1fs %dm%ds %dh%dm %dd%dh}
  def time_delta_abbriv(delta)
    if delta < 60
      TIME_DELTA_STRS[0] % delta
    elsif delta < 3600
      TIME_DELTA_STRS[1] % [delta.to_i / 60, delta.to_i % 60]
    elsif delta < 86400
      TIME_DELTA_STRS[2] % [delta.to_i / 3600, (delta.to_i % 3600) / 60]
    else
      TIME_DELTA_STRS[3] % [delta.to_i / 86400, (delta.to_i % 86400) / 3600]
    end
  end
end
